npm 发布准备:从本地代码到可安装的包
测试通过了不代表别人能用。这节把组件库打包成可以
pnpm add @toimc/vue安装的 npm 包,并把首次发包的全流程交给 AI 完成。
npm 账号、Organization 与 Access Token
npm 之于前端,相当于 Maven 之于 Java、pip 之于 Python——集中管理依赖包的平台。发包前的三件准备工作:注册账号、创建 Organization(拿到 scope 前缀)、生成 Access Token(交给 AI 或 CI 使用)。
注册账号与创建 Organization
- 到 npmjs.com 注册账号
- 左侧栏 Organizations → + Add Organization 创建组织(免费),比如
toimc,作为所有包的 scope 前缀 - scope 命名规则与包名一致:全小写字母、可带中横线。组织关联的包统一发到同一 scope 下(
@toimc/core、@toimc/vue、@toimc/markdown),既统一品牌,也天然避开裸名撞车
公开包免费不限量,私有包需要付费方案(以 npmjs.com 定价页为准)。
创建 Granular Access Token
现在的发包不再用账号密码,而是细粒度访问令牌:头像 → Access Tokens → Generate New Token → Granular Access Token,逐项配置:
| 字段 | 填法 | 说明 |
|---|---|---|
| Token name | 如 devpub | 能区分用途即可 |
| Expiration | 7 / 30 / 90 天或 Custom | 演示用 7 天;CI 长期使用选 Custom 设长 |
| Allowed IP ranges | IP 白名单 | 可留空;限定出口 IP 更安全 |
| Packages and scopes | Read and write | 可授权「所有包」,也可只限一个 scope(推荐只给自己的 scope) |
| Organizations | Read and write | 发组织 scope 的包需要 |
| Bypass 2FA | 按需勾选 | 勾上后此 token 跳过两步验证直接发包,只用于干净可信的 CI/CD 环境 |
点 Generate Token,token 只显示一次,立即复制交给 AI。
2026 年的重要变化(决定你敢不敢勾 Bypass 2FA):
- 2026-07-31 起,勾了 Bypass 2FA 的 granular token 被禁止执行敏感操作:创建/删除 token、修改包的访问权限与维护者、管理组织/团队成员——这些只能交互式 2FA 完成(来源:GitHub Changelog)
- npm 计划 2027 年 1 月起,这类 token 连直接 publish 也将被取消,只保留「读私有包 + 暂存发布」(staged publishing:
npm stage publish提交新版本,维护者用 2FA 审批后生效,2026-09 上线) - 官方推荐自动化发布迁移到 Trusted Publishing(OIDC)——GitHub Actions 用短时 OIDC 令牌发布,不落长期 token,并自动附带 provenance 溯源声明。具体配置见下一节
package.json 发布配置
发布到 npm 的包需要正确的 package.json 配置。关键是 name、version、exports、files 四个字段:
// packages/core/package.json
{
"name": "@toimc/core",
"version": "0.1.0",
"description": "AI Chat UI core layer - types, composables, adapters",
"type": "module",
"main": "./dist/index.cjs",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"import": "./dist/index.js",
"require": "./dist/index.cjs",
"types": "./dist/index.d.ts"
}
},
"files": [
"dist"
],
"sideEffects": false,
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/your-org/ai-chat-ui"
},
"keywords": ["ai", "chat", "vue", "component-library"],
"peerDependencies": {
"vue": "^3.5.0"
}
}
json
关键字段说明:
- name:npm 包名。
@toimc是 scope,需要在 npm 上创建这个 org - exports:定义子路径导出,支持 ESM/CJS 双格式。Node.js 12.11+ 使用这个字段解析导入
- files:只发布
dist目录,不发布源码和测试文件。减小包体积 - sideEffects:设为 false 告诉打包工具这个包的模块没有副作用,可以安全 tree-shake
- peerDependencies:vue 不打进包里,由宿主项目提供。避免 Vue 版本冲突
构建配置
组件库的构建要同时输出 ESM 和 CJS 两种格式,以及 TypeScript 类型声明。
Vite Library Mode
// packages/core/vite.config.ts
import { defineConfig } from 'vite'
import { resolve } from 'path'
export default defineConfig({
build: {
lib: {
entry: resolve(__dirname, 'src/index.ts'),
name: 'AiChatCore',
formats: ['es', 'cjs'],
fileName: (format) => `index.${format === 'es' ? 'js' : 'cjs'}`
},
rollupOptions: {
external: ['vue'],
output: {
globals: {
vue: 'Vue'
}
}
}
}
})
typescript
Vue 组件包需要额外处理 .vue 文件:
// packages/vue/vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { resolve } from 'path'
export default defineConfig({
plugins: [vue()],
build: {
lib: {
entry: resolve(__dirname, 'src/index.ts'),
name: 'AiChatVue',
formats: ['es', 'cjs'],
fileName: (format) => `index.${format === 'es' ? 'js' : 'cjs'}`
},
rollupOptions: {
external: ['vue', '@toimc/core'],
output: {
globals: {
vue: 'Vue',
'@toimc/core': 'AiChatCore'
}
}
}
}
})
typescript
external 把 vue 和 @toimc/core 排除在打包之外——宿主项目会提供这些依赖。
TypeScript 类型声明
用 unplugin-dts(旧名 vite-plugin-dts,2026 年起改名)自动生成 .d.ts 类型文件:
// vite.config.ts
import dts from 'unplugin-dts/vite'
export default defineConfig({
plugins: [
dts({
insertTypesEntry: true,
outDir: 'dist',
}),
],
build: { ... }
})
typescript
安装:pnpm add -D unplugin-dts
构建后 dist/ 目录包含:index.js(ESM)、index.cjs(CJS)、index.d.ts(类型声明)。
版本号管理
npm 使用语义化版本(SemVer):MAJOR.MINOR.PATCH。
- PATCH(0.1.0 → 0.1.1):bug 修复,不改变 API
- MINOR(0.1.0 → 0.2.0):新功能,向后兼容
- MAJOR(0.1.0 → 1.0.0):破坏性变更
# 手动改版本
npm version patch # 0.1.0 → 0.1.1
npm version minor # 0.1.0 → 0.2.0
npm version major # 0.1.0 → 1.0.0
bash
在 Monorepo 中推荐用 changeset 管理版本(下一节讲),但先理解手动方式。
npm 发布流程
首次发布
# 1. 登录 npm(需要 npm 账号)
npm login
# 2. 创建 Organization(如果用 @toimc scope)
# 打开 https://www.npmjs.com/org/create 创建组织 toimc(网页操作,CLI 没有创建组织的命令)
# 3. 构建并发布
pnpm build
npm publish --access public
bash
三个高频坑:
--access public是必须的——scoped 包默认私有(收费),加参数改成公开- 必须发官方源——npmmirror 等国内镜像是只读的,本地 .npmrc 若指向镜像,publish 会失败。发布命令加
--registry https://registry.npmjs.org覆盖,或在项目 .npmrc 写@toimc:registry=https://registry.npmjs.org精确路由 scope - 2FA 账号要 OTP——交互式发布会提示输入一次性验证码:
npm publish --otp=123456;无人值守场景用 granular token(上文)或 OIDC(下一节)
发布前检查清单
1. pnpm test:run # 测试全过
2. pnpm build # 构建成功
3. 检查 dist/ 目录 # 产物完整(js/cjs/d.ts)
4. 检查 package.json # name/version/exports/files 正确
5. npm pack --dry-run # 预览将要发布的文件列表
text
npm pack --dry-run 很有用——它显示哪些文件会被打包进 npm 包,不实际发布。可以检查是否误包含了源码或测试文件。
用 AI 做发布检查
> 请检查 packages/core 和 packages/vue 的 package.json,确保:
> 1. name 和 version 正确
> 2. exports 配置了 ESM 和 CJS 双入口
> 3. files 只包含 dist
> 4. peerDependencies 包含 vue
> 5. sideEffects 设为 false
> 然后运行构建并检查 dist 目录是否完整。
text
用 AI 完成首次发包(实战复盘)
本项目的首次发包全程交给 Claude Code,没有接任何第三方 MCP 工具,主流模型一次跑通。分三步。
第一步:先让 AI 评估「哪些包值得发」
> 我们准备把 ai-chat-ui monorepo 的包发布到 npm(scope @toimc)。
> packages/ 下有 core(核心层)、vue(组件库)、markdown(渲染引擎)等包。
> 请先分析哪些包值得发布、哪些不需要(比如文档站就没必要上 npm),
> 给出发布清单、发布顺序与理由,并把发包流程固化成脚本,方便后续 CI/CD 使用。
text
AI 的结论与实际发布一致:
- 值得发布:
@toimc/core(核心层)、@toimc/vue(组件库)、@toimc/markdown(渲染引擎——Shiki/KaTeX 依赖重、定制性强,独立成包让宿主按需引入)。15-17 章新增的@toimc/agents、@toimc/server后来也进了发布清单 - 不发:
docs(文档站是站点不是包)、playground、dev-server(私有演示与开发服务) - 发布顺序:core → vue → markdown,按依赖方向排,被依赖者先发
- 两种发版模式:changeset 自动模式(AI 推荐,下一节展开)与手动指定版本
- 脚本固化:生成
scripts/publish.sh,--manual 1.0.0手动模式 /--changeset自动模式 /--dry-run模拟运行,CI 里直接调脚本
第二步:让 AI 操作发布
> 请你来操作并发布一个版本。
text
实测:AI 自己对接 npm registry API、按依赖顺序构建发布,三个包一次成功。中途撞到一次包名重复——npm 包名全局唯一、裸名基本被占完,这正是推荐 scope 包名的现实理由之一。AI 还把过程沉淀成复盘文档:遇到的问题、归纳的原因、后续发包命令清单。
scripts/publish.sh 里的防呆设计值得抄:
- 发布前检查 git 工作区干净(
git status --porcelain非空直接退出) - 认证写项目级临时 .npmrc(
@toimc:registry+_authToken),trap退出时自动还原,不污染全局配置 PACKAGES=("core" "vue" "markdown" "agents" "server")数组顺序即发布顺序
顺带一提:ChatGPT 早期做 monorepo 发包,人得先啃 lerna、pnpm workspace、changesets 一堆概念才能动手;现在主流模型用自然语言就能把整套主流方案跑通——知识点从「必须先学」变成了「事后核对 AI 的选择是否合理」。
第三步:发布后自查与 README 空白问题
到 npmjs.com 打开包页面,检查版本、README、导出入口。@toimc/markdown 一开始没有 README,npm 页面一片空白。两种解法:
- 让 AI 写一份(安装、用法、真实 API 节选)
- README 只放一句话简介 + 文档站链接,把用户引到文档站(推荐——文档站才是单一事实来源)
文档站当时还跑在 localhost:5173,要让别人看到就得发到公网——这是 04 节的主题。
.npmignore
不想发布到 npm 的文件,可以用 .npmignore 排除:
# .npmignore
src/
__tests__/
*.test.ts
vitest.config.ts
.github/
text
但如果 files 字段已经限定了只发 dist,.npmignore 就不需要了——files 优先级更高。
小结
- package.json 关键字段:name、exports(ESM+CJS+types)、files、sideEffects、peerDependencies
- Vite Library Mode 构建组件库,external 排除 vue 和 @toimc/core
- unplugin-dts 自动生成 TypeScript 类型声明
- 发布前检查:测试通过 → 构建成功 → npm pack --dry-run 预览 → npm publish
files字段限定只发 dist 目录,比 .npmignore 更精确- Granular Token 是 AI/CI 发包的凭证;Bypass 2FA 只给可信环境,2027 年 1 月起这类 token 将不能直接 publish,提前规划 OIDC 或 staged publishing
- AI 首发实战三步:评估清单 → 固化脚本 → 一次发布成功;包名重复用 scope 解决;无 README 的包页面空白,放文档站链接引流
下一节学习 Changeset 自动化发布——在 Monorepo 中管理版本和 CHANGELOG。
↑